package cc.decat.escript.codegen;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class SymbolTable {
    private SymbolTable parent;
    private final Map<String, Integer> localVarMap;
    private final Map<String, Integer> upVarMap;
    private final List<LocalVarInfo> localVarInfoList;
    private final List<UpVarInfo> upVarInfoList;
    private final BlockGenerate blockGenerate;

    public SymbolTable(BlockGenerate blockGenerate) {
        this.blockGenerate = blockGenerate;
        if(blockGenerate != null && blockGenerate.getParent() != null) {
            this.parent = blockGenerate.getParent().getSymbolTable();
        }
        this.localVarMap = new HashMap<>();
        this.upVarMap = new HashMap<>();
        this.localVarInfoList = new ArrayList<>();
        this.upVarInfoList = new ArrayList<>();
    }

    public int findOrAddLocalVar(String name) {
        if(localVarMap.containsKey(name)) {
            return localVarMap.get(name);
        }
        return addLocalVar(name);
    }

    public int addLocalVar(String name) {
        int index = localVarInfoList.size();
        LocalVarInfo localVarInfo = new LocalVarInfo();
        localVarInfo.setName(name);
        localVarInfo.setIndex(index);
        localVarInfoList.add(localVarInfo);
        localVarMap.put(name, index);
        return index;
    }

    public int addUpVar(VarInfo varInfo) {
        int index = upVarInfoList.size();
        UpVarInfo upVarInfo = new UpVarInfo();
        upVarInfo.setName(varInfo.getName());
        upVarInfo.setIndex(index);
        upVarInfo.setIndexOfParent(varInfo.getIndex());
        upVarInfo.setParentLocal(varInfo instanceof LocalVarInfo);
        upVarInfoList.add(upVarInfo);
        upVarMap.put(varInfo.getName(), index);
        return index;
    }

    public VarInfo findVar(String name) {
        if(localVarMap.containsKey(name)) {
            return localVarInfoList.get(localVarMap.get(name));
        } else if(upVarMap.containsKey(name)) {
            return upVarInfoList.get(upVarMap.get(name));
        }

        VarInfo varInfo;

        if((parent == null) || (varInfo = parent.findVar(name)) == null) {
            return null;
        }

        return upVarInfoList.get(addUpVar(varInfo));
    }

    public SymbolTable getParent() {
        return parent;
    }

    public Map<String, Integer> getLocalVarMap() {
        return localVarMap;
    }

    public Map<String, Integer> getUpVarMap() {
        return upVarMap;
    }

    public List<LocalVarInfo> getLocalVarInfoList() {
        return localVarInfoList;
    }

    public List<UpVarInfo> getUpVarInfoList() {
        return upVarInfoList;
    }

    public BlockGenerate getBlockGenerate() {
        return blockGenerate;
    }
}
